package en.edu.zxj.mq.common;

import en.edu.zxj.mq.mqserver.core.Binding;
import en.edu.zxj.mq.mqserver.core.ExchangeType;
import en.edu.zxj.mq.mqserver.core.Message;
import org.jetbrains.annotations.NotNull;
import org.springframework.util.StringUtils;

/**
 * Created with IntelliJ IDEA.
 * Description：
 *
 * @author: zxj
 * @date: 2024-03-02
 * @time: 12:23:15
 */
public class Router {
    /**
     * @description: 判断 bindingKey 是否合法
     * bindingKey 组成规则如下:
     *      1. 组成: 数字, 字母, 下划线
     *      2. 使用符号 . 将 routingKey 划分成多个部分
     *      3. 支持两种特殊符号作为通配符 (* 和 # 必须是作为被 . 分割出来的独立的部分)
     *          1) *:  * 可以匹配任何一个独立的部分
     *          2) #:  # 监听匹配任何 0 个或者多个独立的部分
     * 形如:
     *      aaa.bbb.ccc
     *      a.1.b
     *      a
     *      #
     *      a.*.b
     **/
    public boolean checkBindingKeyValid(String bindingKey) {
        if (!StringUtils.hasLength(bindingKey)) {
            // null or 空字符串, 合法的情况, 比如在使用 direct / fanout 交换机的时候, bindingKey 用不上, 就可以设为 ""
            return true;
        }
        for (int i = 0; i < bindingKey.length(); i++) {
            char ch = bindingKey.charAt(i);
            // 判断该字符是否是大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判断字符是否是小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判断该字符阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 判断字符是否 '_' 或 '.''
            if (ch == '.' || ch == '_') {
                continue;
            }
            // 判断字符是否为通配符 '*' 或 '#'
            if (ch == '*' || ch == '#') {
                continue;
            }

            // 走到这里, 都不是上述任何一种合法的情况, 就直接返回 false
            return false;
        }

        // 检查 * 或者 # 是否是独立的部分
        // aaa.*.bbb 合法情况, aaa.a*.bbb 非法情况
        String[] words = bindingKey.split("\\.");
        for (String word : words) {
            // 检查 word 长度 > 1 并且包含了 * 或者 #, 就是非法的格式了
            if (word.length() > 1 && (word.contains("*") || word.contains("#"))) {
                return false;
            }
        }

        /*
        // 约定一下, 通配符之间的相邻关系(人为(俺)约定的).
        // 为啥这么约定? 因为前三种相邻的时候, 实现匹配的逻辑会非常繁琐, 同时功能性提升不大~~
        // 1. aaa.#.#.bbb    => 非法
        // 2. aaa.#.*.bbb    => 非法
        // 3. aaa.*.#.bbb    => 非法
        // 4. aaa.*.*.bbb    => 合法
        for (int i = 0; i < words.length - 1; i++) {
            // 连续两个 ##
            if (words[i].equals("#") && words[i + 1].equals("#")) {
                return false;
            }
            // # 连着 *
            if (words[i].equals("#") && words[i + 1].equals("*")) {
                return false;
            }
            // * 连着 #
            if (words[i].equals("*") && words[i + 1].equals("#")) {
                return false;
            }
        }
        */
        return true;
    }

    /**
     * @description: 判断 routingKey 是否合法
     * routingKey 组成规则如下:
     *      1. 组成: 数字, 字母, 下划线
     *      2. 使用符号 . 将 routingKey 划分成多个部分
     * 形如:
     *      aaa.bbb.ccc
     *      a.1.b
     *      a
     **/
    public boolean checkRoutingKeyValid(String routingKey) {
        if (!StringUtils.hasLength(routingKey)) {
            // null or 空字符串, 合法的情况, 比如在使用 fanout 交换机的时候, routingKey 用不上, 就可以设为 ""
            return true;
        }
        for (int i = 0; i < routingKey.length(); i++) {
            char ch = routingKey.charAt(i);
            // 判断该字符是否是大写字母
            if (ch >= 'A' && ch <= 'Z') {
                continue;
            }
            // 判断字符是否是小写字母
            if (ch >= 'a' && ch <= 'z') {
                continue;
            }
            // 判断该字符阿拉伯数字
            if (ch >= '0' && ch <= '9') {
                continue;
            }
            // 判断字符是否 '_' 或 '.'
            if (ch == '.' || ch == '_') {
                continue;
            }

            // 走到这里, 都不是上述任何一种合法的情况, 就直接返回 false
            return false;
        }
        return true;
    }

    /**
     * @description: 路由选择
     * @param: [type 交换机类型, binding 绑定对象 -- 里面提取 routingKey, message 消息对象]
     * @return: 返回该交换机是否可以将该消息转发给绑定的队列中, true 表示可以, false 表示不可以
     **/
    public boolean route(ExchangeType type, Binding binding, Message message) throws MqException {
        // 根据不同的转发类型来进行不同的转发逻辑
        if (type == ExchangeType.FANOUT) {
            // 如果是 FANOUT 类型, 该交换机上所有绑定的队列都需要进行转发
            return true;
        } else if (type == ExchangeType.TOPIC) {
            // 如果是 TOPIC 类型, 规则复杂
            return routeTopic(binding,message);
        } else {
            // 其他情况是不应该存在的
            throw new MqException("[Router] 交换机类型违法! type: " + type);
        }
    }

    private boolean routeTopic(@NotNull Binding binding, @NotNull Message message) {
        String bindingKey = binding.getBindingKey();
        String routingKey = message.getRoutingKey();
        String[] bindingStr = bindingKey.split("\\.");
        String[] routingStr = routingKey.split("\\.");
        return mate(bindingStr,routingStr);
    }

    private boolean mate(String @NotNull [] bindingStr, String @NotNull [] routingStr) {
        int m = bindingStr.length;
        int n = routingStr.length;
        boolean[][] dp = new boolean[m + 1][n + 1];
        dp[0][0] = true;
        for (int i = 0; i < m; i++) {
            if ("#".equals(bindingStr[i])) {
                dp[i+1][0] = true;
            } else {
                break;
            }
        }
        for (int i = 1; i <= m; i++) {
            String wordBinding = bindingStr[i - 1];
            for (int j = 1; j <= n; j++) {
                String wordRouting = routingStr[j - 1];
                if (!"#".equals(wordBinding) && !"*".equals(wordBinding)) {
                    if (wordBinding.equals(wordRouting)) {
                        dp[i][j] = dp[i - 1][j - 1];
                    } else {
                        dp[i][j] = false;
                    }
                } else if ("*".equals(wordBinding)) {
                    dp[i][j] = dp[i - 1][j - 1];
                } else {
                    dp[i][j] = dp[i - 1][j] || dp[i][j - 1];
                }
            }
        }

        return dp[m][n];
    }
}
